#include "HousePOMDemoScene.h"

//To move back to game scene
#include "SceneManager.h"

//Complete forward declarations
#include "SkyDome.h"

//For Wireframe Rendering.
#include "RenderStates.h"

//We create instances of POM Model to render. 
#include "POMModel.h"

HousePOMDemoScene::HousePOMDemoScene()
{
	//Models - init to NULL within the constructor. Memory should be allocated
	//in the OpenScene() function and memroy deleted in CloseScene().  
	skyDome = 0;

	//Init all models to NULL
	for (UINT i = 0; i < HOUSE_DEMO_SCENE_POM_NUM_POM_OBJECTS; i++)
		pomModels[i] = 0;
}

HousePOMDemoScene::~HousePOMDemoScene()
{}

void HousePOMDemoScene::OpenScene()
{
	//Seed random number generator to a known number - thus, each time we run the game, the 
	//point/spot lights will be located and coloured exactly the same. 
	srand(10);

	//Set MSAA State
#ifdef HOUSE_DEMO_SCENE_POM_SCENE_ENABLE_MSAA
	sharedGraphicsInterface->Set4XMSAA(true);
#else
	sharedGraphicsInterface->Set4XMSAA(false);
#endif

	//Init camera position / frustum properties.
	camera->SetPosition(XMFLOAT3(0.0f, 2.0f, 0.0f));
	camera->RebuildViewMatrix();
	OnResize();

	//Load the skydome FIRST
	LoadSkyDome();

	//Load Models (After skydome)
	LoadModels();

	//Init lights
	InitLighting();
	InitDirecLight();
	InitHemisphericalAmbientLight();

	//Initial Scene State is to draw scene as usual.
	sceneState = SCENE_STATE_DRAW_SCENE;
	//Lights initially to be rendered
	shouldRenderLights = true;

	//Reset POM height scale
	pomHeightScale = HOUSE_DEMO_SCENE_POM_INIT_HEIGHT_SCALE;
}

void HousePOMDemoScene::CloseScene()
{
	//Delete POM Models
	for (UINT i = 0; i < HOUSE_DEMO_SCENE_POM_NUM_POM_OBJECTS; i++)
		SafeDelete(pomModels[i]);

	//Delete models
	SafeDelete(skyDome);
}

void HousePOMDemoScene::LoadModels()
{
	Material m;
	m.Ambient = XMFLOAT4(0.0f, 0.0f, 0.0f, 1.0f);
	m.Diffuse = XMFLOAT4(0.6f, 0.6f, 0.6f, 1.0f);
	m.Reflection = XMFLOAT4(0.0f, 0.0f, 0.0f, 1.0f);
	m.Specular = XMFLOAT4(0.6f, 0.6f, 0.6f, 8.0f);

	//Load POM models
	//
	//Floor
	pomModels[0] = new POMModel();		
	pomModels[0]->InitAsGrid(sharedGraphicsInterface->GetDevice(), 
		15, 15, 2, 2,
		L"Textures/GLDemo/rocks_diffuse.jpg", 
		L"Textures/GLDemo/rocks_NM_height.png", 
		L"", false, 
		L"", L"", false);

	//Init location
	pomModels[0]->SetWorldMatrix(XMFLOAT3(0.0f, 0.0f, 0.0f), 
		XMFLOAT3(0.0f, 0.0f, 0.0f), XMFLOAT3(1.0f, 1.0f, 1.0f));

	//Set tex scaling
	pomModels[0]->SetTextureTransform(XMFLOAT2(7.5f, 7.5f));

	//Backwall.
	pomModels[1] = new POMModel();		
	pomModels[1]->InitAsGrid(sharedGraphicsInterface->GetDevice(), 
		15, 15, 2, 2,
		L"Textures/Terrain/Rock_9/d.png", 
		L"Textures/Terrain/Rock_9/n.png", 
		L"Textures/Terrain/Rock_9/h.png", true,  //Using seperate HM. 
		L"Textures/Terrain/Rock_9/s.png",
		L"Textures/Terrain/Rock_9/g.png", true); //Use spec map

	//Init location
	pomModels[1]->SetWorldMatrix(XMFLOAT3(0.0f, 0.0f, 7.5f), 
		XMFLOAT3(MathHelper::ToRadians(270.0f), 0.0f, 0.0f), XMFLOAT3(1.0f, 1.0f, 1.0f));

	//Set tex scaling
	pomModels[1]->SetTextureTransform(XMFLOAT2(7.5f, 7.5f));

	//Cube
	pomModels[2] = new POMModel();		
	pomModels[2]->InitAsBox(sharedGraphicsInterface->GetDevice(), 
		1, 1, 1,
		L"Textures/GLDemo/four_diffuse.jpg", 
		L"Textures/GLDemo/four_NM_height.png", 
		L"", false,
		L"", L"", false);

	//Init location
	pomModels[2]->SetWorldMatrix(XMFLOAT3(0.0f, 2.0f, 5.0f), 
		XMFLOAT3(0.0f, 0.0f, 0.0f), XMFLOAT3(1.0f, 1.0f, 1.0f));

	//Set tex scaling
	pomModels[2]->SetTextureTransform(XMFLOAT2(1.0f, 1.0f));

	//Rightwall.
	pomModels[3] = new POMModel();		
	pomModels[3]->InitAsGrid(sharedGraphicsInterface->GetDevice(), 
		15, 15, 2, 2,
		L"Textures/GLDemo/stones_diffuse.jpg", 
		L"Textures/GLDemo/stones_NM_height.png", 
		L"", false, 
		L"", L"", false);

	//Init location
	pomModels[3]->SetWorldMatrix(XMFLOAT3(7.5f, 0.0f, 0.0f), 
		XMFLOAT3(MathHelper::ToRadians(90.0f), MathHelper::ToRadians(270.0f), 0.0f), XMFLOAT3(1.0f, 1.0f, 1.0f));

	//Set tex scaling
	pomModels[3]->SetTextureTransform(XMFLOAT2(7.5f, 7.5f));

	//Set shared data
	for (UINT i = 0; i < HOUSE_DEMO_SCENE_POM_NUM_POM_OBJECTS; i++)
	{
		//Update material.
		pomModels[i]->SetMaterial(m);

		//Set POM Data
		pomModels[i]->SetPOMData(pomHeightScale,
			HOUSE_DEMO_SCENE_POM_MIN_SAMPLES, 
			HOUSE_DEMO_SCENE_POM_MAX_SAMPLES);
	}
}

void HousePOMDemoScene::LoadSkyDome()
{
	skyDome = new SkyDome(sharedGraphicsInterface->GetDevice(),
		//L"Terrain/QuantumTheory/QuantumHighlands/Textures/SkyDome/cubeMap.dds", 
		L"Textures/snowcube1024.dds",
		camera);
	sharedGraphicsInterface->SetCurrentSkyDome(skyDome);
}

void HousePOMDemoScene::InitLighting()
{
	//Set scene lighting. Randomly place lights in a grid: 40x10x60
	//Done using new to avoid stack overflow issues when we have > 3000 lights
	//or so.
	TileBasedLight* lightArr = new TileBasedLight[HOUSE_DEMO_SCENE_POM_NUM_LIGHTS];
	//Loop through lights and set properties.
	for (UINT i = 0; i < HOUSE_DEMO_SCENE_POM_NUM_LIGHTS; i++)
	{
		//No colour output. 
		XMFLOAT4 null = XMFLOAT4(0.0f, 0.0f, 0.0f, 1.0f);
		//Random diffuse and spec colour.
		XMFLOAT4 diffDiffCol = XMFLOAT4(MathHelper::RandF(), MathHelper::RandF(), 
			MathHelper::RandF(), 1.0f);
		XMFLOAT4 diffSpecCol = diffDiffCol;

		//Ambient term
		XMFLOAT4 amb = XMFLOAT4(diffSpecCol.x/5.0f, diffSpecCol.y/5.0f, 
			diffSpecCol.z/5.0f, 1.0f);
		//Random location.
		XMFLOAT3 randLoc = XMFLOAT3(MathHelper::RandF(-7.5f, 7.5f),
			MathHelper::RandF(0.1f, 4.5f), MathHelper::RandF(-7.5f, 7.5f));

		//Randon range.
		float randRange = MathHelper::RandF(1.0f, 5.0f);
		//Create point lights. 
		lightArr[i].InitPointLight(amb, diffDiffCol, diffSpecCol, 
			randLoc, randRange);
	}

	//Set scene lighting - creating our required buffers - we want the option
	//to visualise our lights, so set the required bool to true.
	sharedGraphicsInterface->SetSceneLights(&lightArr[0], HOUSE_DEMO_SCENE_POM_NUM_LIGHTS, true);

	//Delete dynamically allocated light array.
	delete []lightArr;
}

void HousePOMDemoScene::InitDirecLight()
{
	//Set Directional light. 
	dLight.Ambient = XMFLOAT4(0.06f, 0.06f, 0.06f, 1.0f);
	dLight.Diffuse = XMFLOAT4(0.2f, 0.2f, 0.2f, 1.0f);
	dLight.Specular = XMFLOAT4(0.5f, 0.5f, 0.5f, 128.0f);
	dLight.Direction = XMFLOAT3(0.75f, -0.32f, +1.0f);

	//Set single directional light 
	sharedGraphicsInterface->SetDirectionalLights(&dLight, 1, 0);
}

void HousePOMDemoScene::InitHemisphericalAmbientLight()
{
	//sharedGraphicsInterface->SetHemisphericAmbientColours(XMFLOAT3(0.0f, 0.0f, 0.0f), 
	//	XMFLOAT3(0.298f, 0.4078f, 0.4745f));
	//sharedGraphicsInterface->SetHemisphericAmbientColours(XMFLOAT3(0.0f, 0.0f, 0.0f), 
	//	XMFLOAT3(0.600f, 0.8508f, 0.917f));
	sharedGraphicsInterface->SetHemisphericAmbientColours(XMFLOAT3(0.0f, 0.0f, 0.0f), 
		XMFLOAT3(1.0f, 1.0f, 1.0f));
	//sharedGraphicsInterface->SetHemisphericAmbientColours(XMFLOAT3(1.0f, 0.5f, 0.15f), 
	//	XMFLOAT3(1.0f, 1.0f, 1.0f));
}

void HousePOMDemoScene::OnMouseDown(WPARAM btnState, int x, int y, UINT originalMessage)
{
	AbstractScene::OnMouseDown(btnState, x, y, originalMessage);
}

void HousePOMDemoScene::OnMouseUp(WPARAM btnState, int x, int y, UINT originalMessage)
{}

void HousePOMDemoScene::OnMouseMove(WPARAM btnState, int x, int y)
{
	//Rather than use default method which will (eventually) lock the cameras
	//pitch to a certain range, use my own systems. 
	if( (btnState & MK_LBUTTON) != 0 )
	{
		// Make each pixel correspond to a quarter of a degree.
		float dx = 0.25f*static_cast<float>(x - lastMPos.x);
		float dy = 0.25f*static_cast<float>(y - lastMPos.y);

		camera->AddPitch(dy);
		camera->AddYaw(dx);
	} 

	lastMPos.x = x;
	lastMPos.y = y;
}

void HousePOMDemoScene::OnResize()
{
	//Use custom set of frusutm properties. 
	camera->SetFrustumProperties(DEFAULT_CAMERA_FRUSTUM_FOVY, 
		sharedGraphicsInterface->ReturnBackBufferAspectRatio(),
		0.1f, 100.0f); 
	camera->RebuildViewMatrix();
}

void HousePOMDemoScene::UpdateWithDelta(float delta)
{
	//Timer updated every frame - used to prevent the user
	//from changing height scale factor too often
	static float pomHeightTimer = 0.0f;
	pomHeightTimer+=delta;

	//Manually move camer (Ignores the camera movement
	//multiplier)
	if( GetAsyncKeyState('W') & 0x8000 )
		camera->Walk(3.0f*delta*1.0f);

	if( GetAsyncKeyState('S') & 0x8000 )
		camera->Walk(-3.0f*delta*1.0f);

	if( GetAsyncKeyState('A') & 0x8000 )
		camera->Strafe(-3.0f*delta*1.0f);

	if( GetAsyncKeyState('D') & 0x8000 )
		camera->Strafe(3.0f*delta*1.0f);

	//Rebuild camera view matrix
	camera->RebuildViewMatrix();

	//Update D Light. 
	sharedGraphicsInterface->SetDirectionalLights(&dLight, 1, 0);

	//On X or Z, update the height scale. (Though, only update a few times
	//per second). 
	if (pomHeightTimer > 1.0f / 5.0f)
	{
		if ( GetAsyncKeyState('X') & 0x8000 )
		{
			//Add.
			pomHeightScale+=0.01f;

			//And reset timer.
			pomHeightTimer = 0.0f;

			//Output new height
			std::stringstream ss;
			ss << pomHeightScale;
			OutputDebugStringW(L"New POM Height Scale = ");
			OutputDebugStringA(ss.str().c_str());
			OutputDebugStringW(L"\n");
		}
		else if ( GetAsyncKeyState('Z') & 0x8000 )
		{
			//Subtract
			pomHeightScale-=0.01f;

			//Cap to 0.0f
			if (pomHeightScale < 0.0f)
				pomHeightScale = 0.0f;

			//And reset timer.
			pomHeightTimer = 0.0f;

			//Output new height
			std::stringstream ss;
			ss << pomHeightScale;
			OutputDebugStringW(L"New POM Height Scale = ");
			OutputDebugStringA(ss.str().c_str());
			OutputDebugStringW(L"\n");
		}
	}

	//Decide what to render?
	if( GetAsyncKeyState('1') & 0x8000 )
		sceneState = SCENE_STATE_DRAW_SCENE; 
	if( GetAsyncKeyState('2') & 0x8000 )
		sceneState = SCENE_STATE_DRAW_Z_PRE_PASS_DEPTH_BUFFER; 
	if ( GetAsyncKeyState('3') & 0x8000 )
		sceneState = SCENE_STATE_DRAW_CS_MAX_Z_DEPTH_BUFFER;
	if ( GetAsyncKeyState('4') & 0x8000 )
		sceneState = SCENE_STATE_DRAW_CS_LLSEB_GREYSCALE;
	if ( GetAsyncKeyState('5') & 0x8000 )
		sceneState = SCENE_STATE_DRAW_CS_LLSEB_COLOUR;
	if ( GetAsyncKeyState('6') & 0x8000 )
		sceneState = SCENE_STATE_DRAW_CS_LLSEBT_GREYSCALE;
	if ( GetAsyncKeyState('7') & 0x8000 )
		sceneState = SCENE_STATE_DRAW_CS_LLSEBT_COLOUR;
	if ( GetAsyncKeyState('8') & 0x8000 )
		sceneState = SCENE_STATE_DRAW_SSAO_VIEW_NORMALS;
	if ( GetAsyncKeyState('9') & 0x8000 )
		sceneState = SCENE_STATE_DRAW_SSAO_VIEW_DEPTHS;
	if ( GetAsyncKeyState('0') & 0x8000 )
		sceneState = SCENE_STATE_DRAW_SSAO_MAP;
	if ( GetAsyncKeyState('L') & 0x8000 )
		sceneState = SCENE_STATE_DRAW_SHADOW_DEPTH;

	//Render the lights?
	if ( GetAsyncKeyState('G') & 0x8000 )
		shouldRenderLights = true;
	if ( GetAsyncKeyState('F') & 0x8000 )
		shouldRenderLights = false;

	//Loop theough POM models and update the POM data
	for (UINT i = 0; i < HOUSE_DEMO_SCENE_POM_NUM_POM_OBJECTS; i++)
		pomModels[i]->SetPOMData(pomHeightScale,
			HOUSE_DEMO_SCENE_POM_MIN_SAMPLES, 
			HOUSE_DEMO_SCENE_POM_MAX_SAMPLES);

	//Should go back to game scene?
	if ( GetAsyncKeyState('E') & 0x8000 )
	{
		//Debug message
		OutputDebugStringW(L"House Demo Scene - Transitioning back to the Game Scene!\n");
		
		//Tranaition back to game scene - we will unload this scenes data but, since the 
		//game scene has already had its data loaded and stored in GPU memory, we do not
		//need to reload the game scene. 
		SceneManager::GetInstance()->TransitionToSceneWithKey("Roo's World", true, false);
	}
}

void HousePOMDemoScene::RenderScene()
{
	//Start rendering - Clear backbuffer and depth stencil view - Scene manually
	//updates shadow map (NOT the engine as we do in the game)
	float clearColGrey[4] = {0.6f, 0.6f, 0.6f, 1.0f};
	sharedGraphicsInterface->StartFrame(clearColGrey, camera, true);

	//Add to batch...
	for (unsigned i = 0; i < HOUSE_DEMO_SCENE_POM_NUM_POM_OBJECTS; i++)
		sharedGraphicsInterface->AddModelToBatch((AbstractAdvancedModel*)pomModels[i]);

	//Render the 3D scene.
	sharedGraphicsInterface->Render(shouldRenderLights);

	//Begin 2D rendering (Text/HUD)
	sharedGraphicsInterface->Begin2DRendering();
	//End of text rendering
	sharedGraphicsInterface->End2DRendering();
	
	//Debug Rendering
	//
	//Draw depth buffer (filled from Z Pre Pass).
	if (sceneState == SCENE_STATE_DRAW_Z_PRE_PASS_DEPTH_BUFFER)
		sharedGraphicsInterface->DrawZPrePassFilledDepthBuffer();
	//Draw max tile depth texture.
	else if (sceneState == SCENE_STATE_DRAW_CS_MAX_Z_DEPTH_BUFFER)
		sharedGraphicsInterface->DrawTileMaxZBuffer(); 
	else if (sceneState == SCENE_STATE_DRAW_CS_LLSEB_GREYSCALE)
		sharedGraphicsInterface->DrawLightListStartEndBufferGreyscale();
	else if (sceneState == SCENE_STATE_DRAW_CS_LLSEB_COLOUR)
		sharedGraphicsInterface->DrawLightListStartEndBufferColour();
	else if (sceneState == SCENE_STATE_DRAW_CS_LLSEBT_GREYSCALE)
		sharedGraphicsInterface->DrawLightListStartEndBufferTransparantGreyscale();
	else if (sceneState == SCENE_STATE_DRAW_CS_LLSEBT_COLOUR)
		sharedGraphicsInterface->DrawLightListStartEndBufferTransparantColour();
	else if (sceneState == SCENE_STATE_DRAW_SSAO_VIEW_NORMALS)
		sharedGraphicsInterface->DrawSSAOViewNormals();
	else if (sceneState == SCENE_STATE_DRAW_SSAO_VIEW_DEPTHS)
		sharedGraphicsInterface->DrawSSAOViewDepths();
	else if (sceneState == SCENE_STATE_DRAW_SSAO_MAP)
		sharedGraphicsInterface->DrawSSAOMap();
	else if (sceneState == SCENE_STATE_DRAW_SHADOW_DEPTH)
		sharedGraphicsInterface->DrawShadowMapDepth();

	//Present the backbuffer (if in full screen, present with vsync on)
	bool fsState = sharedGraphicsInterface->IsInFullScreenMode();
	sharedGraphicsInterface->EndFrame(fsState);
}